/*
 * Copyright (C) 2011-2013 GUIGUI Simon, fyhertz@gmail.com
 *
 * This file is part of Spydroid (http://code.google.com/p/spydroid-ipcamera/)
 *
 * Spydroid is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This source code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package net.majorkernelpanic.spydroid.api;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.AudioManager;
import android.media.SoundPool;
import android.media.SoundPool.OnLoadCompleteListener;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.Log;

import net.majorkernelpanic.spydroid.R;
import net.majorkernelpanic.spydroid.SpydroidApplication;
import net.majorkernelpanic.streaming.SessionBuilder;
import net.majorkernelpanic.streaming.video.VideoQuality;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.lang.reflect.Field;

/**
 * Used by the {@link CustomHttpServer}.
 */
public class RequestHandler {

    public final static String TAG = "RequestHandler";

    private final static SoundPool mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);

    static {
        mSoundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
                soundPool.play(sampleId, 0.99f, 0.99f, 1, 0, 1);
            }
        });
    }

    /**
     * Executes a batch of requests and returns all the results
     *
     * @param request Contains a json containing one or more requests
     * @return A JSON to send back
     */
    public static String handle(String request) {
        StringBuilder response = new StringBuilder();
        JSONTokener tokener = new JSONTokener(request);

        try {
            Object token = tokener.nextValue();

            response.append("{");

            // More than one request to execute
            if (token instanceof JSONArray) {
                JSONArray array = (JSONArray) token;
                for (int i = 0; i < array.length(); i++) {
                    JSONObject object = (JSONObject) array.get(i);
                    response.append("\"" + object.getString("action") + "\":");
                    exec(object, response);
                    if (i != array.length() - 1) response.append(",");
                }
                // Only One request
            } else if (token instanceof JSONObject) {
                JSONObject object = (JSONObject) token;
                response.append("\"" + object.getString("action") + "\":");
                exec(object, response);
            }

            response.append("}");

        } catch (Exception e) {
            // Pokemon, gotta catch'em all !
            Log.e(TAG, "Invalid request: " + request);
            e.printStackTrace();
            return "INVALID REQUEST";
        }

        Log.d(TAG, "Request: " + request);
        Log.d(TAG, "Answer: " + response.toString());

        return response.toString();
    }

    /**
     * The implementation of all the possible requests is here
     * -> "sounds": returns a list of available sounds on the phone
     * -> "screen": returns the screen state (whether the app. is on the foreground or not)
     * -> "play": plays a sound on the phone
     * -> "set": update Spydroid's configuration
     * -> "get": returns Spydroid's configuration (framerate, bitrate...)
     * -> "state": returns a JSON containing information about the state of the application
     * -> "battery": returns an approximation of the battery level on the phone
     * -> "buzz": makes the phone buuz
     * -> "volume": sets or gets the volume
     *
     * @throws JSONException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     **/
    static private void exec(JSONObject object, StringBuilder response) throws JSONException, IllegalArgumentException, IllegalAccessException {

        SpydroidApplication application = SpydroidApplication.getInstance();
        Context context = application.getApplicationContext();

        String action = object.getString("action");

        // Returns a list of available sounds on the phone
        if (action.equals("sounds")) {
            Field[] raws = R.raw.class.getFields();
            response.append("[");
            for (int i = 0; i < raws.length - 1; i++) {
                response.append("\"" + raws[i].getName() + "\",");
            }
            response.append("\"" + raws[raws.length - 1].getName() + "\"]");
        }

        // Returns the screen state (whether the app. is on the foreground or not)
        else if (action.equals("screen")) {
            response.append(application.applicationForeground ? "\"1\"" : "\"0\"");
        }

        // Plays a sound on the phone
        else if (action.equals("play")) {
            Field[] raws = R.raw.class.getFields();
            for (int i = 0; i < raws.length; i++) {
                if (raws[i].getName().equals(object.getString("name"))) {
                    mSoundPool.load(application, raws[i].getInt(null), 0);
                }
            }
            response.append("[]");
        }

        // Returns Spydroid's configuration (framerate, bitrate...)
        else if (action.equals("get")) {
            final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);

            response.append("{\"streamAudio\":" + settings.getBoolean("stream_audio", false) + ",");
            response.append("\"audioEncoder\":\"" + (application.audioEncoder == SessionBuilder.AUDIO_AMRNB ? "AMR-NB" : "AAC") + "\",");
            response.append("\"streamVideo\":" + settings.getBoolean("stream_video", true) + ",");
            response.append("\"videoEncoder\":\"" + (application.videoEncoder == SessionBuilder.VIDEO_H263 ? "H.263" : "H.264") + "\",");
            response.append("\"videoResolution\":\"" + application.videoQuality.resX + "x" + application.videoQuality.resY + "\",");
            response.append("\"videoFramerate\":\"" + application.videoQuality.framerate + " fps\",");
            response.append("\"videoBitrate\":\"" + application.videoQuality.bitrate / 1000 + " kbps\"}");

        }

        // Update Spydroid's configuration
        else if (action.equals("set")) {
            final JSONObject settings = object.getJSONObject("settings");
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            final Editor editor = prefs.edit();

            editor.putBoolean("stream_video", settings.getBoolean("stream_video"));
            application.videoQuality = VideoQuality.parseQuality(settings.getString("video_quality"));
            editor.putInt("video_resX", application.videoQuality.resX);
            editor.putInt("video_resY", application.videoQuality.resY);
            editor.putString("video_framerate", String.valueOf(application.videoQuality.framerate));
            editor.putString("video_bitrate", String.valueOf(application.videoQuality.bitrate / 1000));
            editor.putString("video_encoder", settings.getString("video_encoder").equals("H.263") ? "2" : "1");
            editor.putBoolean("stream_audio", settings.getBoolean("stream_audio"));
            editor.putString("audio_encoder", settings.getString("audio_encoder").equals("AMR-NB") ? "3" : "5");
            editor.commit();
            response.append("[]");

        }

        // Returns a JSON containing information about the state of the application
        else if (action.equals("state")) {

            Exception exception = application.lastCaughtException;

            response.append("{");

            if (exception != null) {

                // Used to display the message on the user interface
                String lastError = exception.getMessage();

                // Useful to display additional information to the user depending on the error
                StackTraceElement[] stack = exception.getStackTrace();
                StringBuilder builder = new StringBuilder(exception.getClass().getName() + " : " + lastError + "||");
                for (int i = 0; i < stack.length; i++)
                    builder.append("at " + stack[i].getClassName() + "." + stack[i].getMethodName() + " (" + stack[i].getFileName() + ":" + stack[i].getLineNumber() + ")||");

                response.append("\"lastError\":\"" + (lastError != null ? lastError : "unknown error") + "\",");
                response.append("\"lastStackTrace\":\"" + builder.toString() + "\",");

            }

            response.append("\"activityPaused\":\"" + (application.applicationForeground ? "1" : "0") + "\"");
            response.append("}");

        } else if (action.equals("clear")) {
            application.lastCaughtException = null;
            response.append("[]");
        }

        // Returns an approximation of the battery level
        else if (action.equals("battery")) {
            response.append("\"" + application.batteryLevel + "\"");
        }

        // Makes the phone vibrates for 300ms
        else if (action.equals("buzz")) {
            Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
            vibrator.vibrate(300);
            response.append("[]");
        }

        // Sets or gets the system's volume
        else if (action.equals("volume")) {
            AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
            if (object.has("set")) {
                audio.setStreamVolume(AudioManager.STREAM_MUSIC, object.getInt("set"), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                response.append("[]");
            } else {
                int max = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
                int current = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
                response.append("{\"max\":" + max + ",\"current\":" + current + "}");
            }
        }

    }

}
